{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Download training data from open datasets.\n",
    "training_data = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor(),\n",
    ")\n",
    "\n",
    "# Download test data from open datasets.\n",
    "test_data = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor(),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28])\n",
      "Shape of y: torch.Size([64]) torch.int64\n"
     ]
    }
   ],
   "source": [
    "batch_size = 64\n",
    "\n",
    "# Create data loaders.\n",
    "train_dataloader = DataLoader(training_data, batch_size=batch_size)\n",
    "test_dataloader = DataLoader(test_data, batch_size=batch_size)\n",
    "\n",
    "for X, y in test_dataloader:\n",
    "    print(f\"Shape of X [N, C, H, W]: {X.shape}\")\n",
    "    print(f\"Shape of y: {y.shape} {y.dtype}\")\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using cpu device\n",
      "NeuralNetwork(\n",
      "  (flatten): Flatten(start_dim=1, end_dim=-1)\n",
      "  (linear_relu_stack): Sequential(\n",
      "    (0): Linear(in_features=784, out_features=512, bias=True)\n",
      "    (1): ReLU()\n",
      "    (2): Linear(in_features=512, out_features=512, bias=True)\n",
      "    (3): ReLU()\n",
      "    (4): Linear(in_features=512, out_features=10, bias=True)\n",
      "  )\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "print(f\"Using {device} device\")\n",
    "\n",
    "\n",
    "# Define model\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.flatten = nn.Flatten()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(512, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(512, 10),\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.flatten(x)\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        return logits\n",
    "\n",
    "\n",
    "model = NeuralNetwork().to(device)\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "loss_fn = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(dataloader, model, loss_fn, optimizer):\n",
    "    size = len(dataloader.dataset)\n",
    "    model.train()\n",
    "    for batch, (X, y) in enumerate(dataloader):\n",
    "        X, y = X.to(device), y.to(device)\n",
    "\n",
    "        # Compute prediction error\n",
    "        pred = model(X)\n",
    "        loss = loss_fn(pred, y)\n",
    "\n",
    "        # Backpropagation\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        if batch % 100 == 0:\n",
    "            loss, current = loss.item(), (batch + 1) * len(X)\n",
    "            print(f\"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(dataloader, model, loss_fn):\n",
    "    size = len(dataloader.dataset)\n",
    "    num_batches = len(dataloader)\n",
    "    model.eval()\n",
    "    test_loss, correct = 0, 0\n",
    "    with torch.no_grad():\n",
    "        for X, y in dataloader:\n",
    "            X, y = X.to(device), y.to(device)\n",
    "            pred = model(X)\n",
    "            test_loss += loss_fn(pred, y).item()\n",
    "            correct += (pred.argmax(1) == y).type(torch.float).sum().item()\n",
    "    test_loss /= num_batches\n",
    "    correct /= size\n",
    "    print(\n",
    "        f\"Test Error: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\"\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1\n",
      "-------------------------------\n",
      "loss: 2.297016  [   64/60000]\n",
      "loss: 2.295478  [ 6464/60000]\n",
      "loss: 2.278597  [12864/60000]\n",
      "loss: 2.277763  [19264/60000]\n",
      "loss: 2.256855  [25664/60000]\n",
      "loss: 2.223153  [32064/60000]\n",
      "loss: 2.236408  [38464/60000]\n",
      "loss: 2.201923  [44864/60000]\n",
      "loss: 2.192984  [51264/60000]\n",
      "loss: 2.170577  [57664/60000]\n",
      "Test Error: \n",
      " Accuracy: 45.2%, Avg loss: 2.168324 \n",
      "\n",
      "Epoch 2\n",
      "-------------------------------\n",
      "loss: 2.168288  [   64/60000]\n",
      "loss: 2.162671  [ 6464/60000]\n",
      "loss: 2.113698  [12864/60000]\n",
      "loss: 2.134506  [19264/60000]\n",
      "loss: 2.080728  [25664/60000]\n",
      "loss: 2.024130  [32064/60000]\n",
      "loss: 2.051025  [38464/60000]\n",
      "loss: 1.979278  [44864/60000]\n",
      "loss: 1.974126  [51264/60000]\n",
      "loss: 1.911821  [57664/60000]\n",
      "Test Error: \n",
      " Accuracy: 57.9%, Avg loss: 1.913734 \n",
      "\n",
      "Epoch 3\n",
      "-------------------------------\n",
      "loss: 1.937105  [   64/60000]\n",
      "loss: 1.907030  [ 6464/60000]\n",
      "loss: 1.803851  [12864/60000]\n",
      "loss: 1.844997  [19264/60000]\n",
      "loss: 1.730146  [25664/60000]\n",
      "loss: 1.681423  [32064/60000]\n",
      "loss: 1.700729  [38464/60000]\n",
      "loss: 1.608371  [44864/60000]\n",
      "loss: 1.621684  [51264/60000]\n",
      "loss: 1.517763  [57664/60000]\n",
      "Test Error: \n",
      " Accuracy: 61.3%, Avg loss: 1.543385 \n",
      "\n",
      "Epoch 4\n",
      "-------------------------------\n",
      "loss: 1.602129  [   64/60000]\n",
      "loss: 1.562116  [ 6464/60000]\n",
      "loss: 1.424572  [12864/60000]\n",
      "loss: 1.496564  [19264/60000]\n",
      "loss: 1.372666  [25664/60000]\n",
      "loss: 1.363127  [32064/60000]\n",
      "loss: 1.377507  [38464/60000]\n",
      "loss: 1.309548  [44864/60000]\n",
      "loss: 1.336829  [51264/60000]\n",
      "loss: 1.234680  [57664/60000]\n",
      "Test Error: \n",
      " Accuracy: 63.2%, Avg loss: 1.269810 \n",
      "\n",
      "Epoch 5\n",
      "-------------------------------\n",
      "loss: 1.341510  [   64/60000]\n",
      "loss: 1.314376  [ 6464/60000]\n",
      "loss: 1.163100  [12864/60000]\n",
      "loss: 1.267638  [19264/60000]\n",
      "loss: 1.138296  [25664/60000]\n",
      "loss: 1.157440  [32064/60000]\n",
      "loss: 1.179528  [38464/60000]\n",
      "loss: 1.125515  [44864/60000]\n",
      "loss: 1.159601  [51264/60000]\n",
      "loss: 1.071132  [57664/60000]\n",
      "Test Error: \n",
      " Accuracy: 64.6%, Avg loss: 1.099838 \n",
      "\n",
      "Done!\n"
     ]
    }
   ],
   "source": [
    "epochs = 5\n",
    "for t in range(epochs):\n",
    "    print(f\"Epoch {t+1}\\n-------------------------------\")\n",
    "    train(train_dataloader, model, loss_fn, optimizer)\n",
    "    test(test_dataloader, model, loss_fn)\n",
    "print(\"Done!\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mytorchenv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.21"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
